package cn.starrys.tool.mail

import cn.starrys.tool.mail.entity.Mail
import cn.starrys.tool.mail.entity.Mail.Addressee
import cn.starrys.tool.mail.entity.Mail.Type
import cn.starrys.tool.mail.entity.MailProps
import cn.starrys.tool.mail.entity.MailProps.Protocols
import jakarta.mail.Message
import jakarta.mail.MessagingException
import jakarta.mail.Session
import jakarta.mail.Transport
import jakarta.mail.internet.InternetAddress
import jakarta.mail.internet.MimeBodyPart
import jakarta.mail.internet.MimeMessage
import jakarta.mail.internet.MimeMultipart
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
import java.util.*

/**
 * 邮件工具。
 * @param mps 邮件配置。
 */
@Suppress("MemberVisibilityCanBePrivate")
class MailTools(private val mps: MailProps) {

    // Logger
    private val logger: Logger = LoggerFactory.getLogger(javaClass)

    // 缓存发信协议的 Session
    private val smtp: Session by lazy { getSession(Protocols.SMTP) }


    /**
     * 重载构造。
     * @param host     主机。
     * @param port     主机端口。
     * @param from     发件邮箱。
     * @param password 发件邮箱授权码。
     * @param nickname 昵称。
     * @param ssl      启用 ssl。
     */
    constructor(host: String, port: Int, from: String, password: String, nickname: String?, ssl: Boolean) :
            this(MailProps(host, port, from, password, nickname, ssl, false, Charsets.UTF_8, true))

    /**
     * 重载构造。
     * @param host     主机。
     * @param port     主机端口。
     * @param from     发件邮箱。
     * @param password 发件邮箱授权码。
     * @param nickname 昵称。
     */
    constructor(host: String, port: Int, from: String, password: String, nickname: String?) :
            this(host, port, from, password, nickname, true)

    /**
     * 重载构造。
     * @param host     主机。
     * @param port     主机端口。
     * @param from     发件邮箱。
     * @param password 发件邮箱授权码。
     */
    constructor(host: String, port: Int, from: String, password: String) :
            this(host, port, from, password, null)


    /**
     * 获取 [Session] 对象.
     * @param protocol 指定协议
     * @return [Session]对象。
     */
    fun getSession(protocol: Protocols): Session {
        val props = Properties().apply {
            // 邮箱服务器地址
            put("mail.host", mps.host)
            // 端口
            put("mail.${protocol}.port", mps.port)
            // 发件邮箱
            put("mail.from", mps.from)
            // 发件邮箱授权码
            put("mail.password", mps.password)
            // 发信昵称
            mps.nickname?.let { put("mail.user", it) }
            // 协议
            when (protocol) {
                // 接收邮件
                Protocols.POP3, Protocols.IMAP -> put("mail.store.protocol", "$protocol")
                // 发送邮件
                Protocols.SMTP -> put("mail.transport.protocol", "$protocol")
            }
            // 认证
            put("mail.${protocol}.auth", "${mps.auth}")
            // ssl
            put("mail.${protocol}.ssl.enable", "${mps.ssl}")
            put("mail.${protocol}.ssl.socketFactory", "org.eclipse.angus.mail.util.MailSSLSocketFactory")
            // 调试
            put("mail.debug", "${mps.debug}")
        }
        if (mps.auth) return Session.getInstance(props, LoginAuthenticator(mps.from, mps.password))
        return Session.getInstance(props)
    }

    /**
     * 创建 [InternetAddress] 对象。
     * @param addresseeList [Addressee] 邮件收件人列表。
     * @return [InternetAddress] 对象数组。
     */
    fun cia(addresseeList: List<Addressee>): Array<InternetAddress> {
        return addresseeList.map { ma ->
            InternetAddress(ma.addressee, ma.nickname, mps.charset.name())
        }.toTypedArray()
    }

    /**
     * 创建邮件。
     * @param session [Session]对象。
     * @param mail    [Mail] 邮件内容。
     * @return 一封邮件。
     */
    fun createMimeMessage(session: Session, mail: Mail): MimeMessage {
        val mimeMessage = MimeMessage(session)
        // 发件人
        mimeMessage.setFrom(InternetAddress(mps.from, mps.nickname, mps.charset.name()))
        // 收件人
        mimeMessage.setRecipients(Message.RecipientType.TO, cia(mail.to))
        // 抄送人
        mail.cc?.let { mimeMessage.setRecipients(Message.RecipientType.CC, cia(it)) }
        // 密送人
        mail.bcc?.let { mimeMessage.setRecipients(Message.RecipientType.BCC, cia(it)) }
        // 邮件主题
        mimeMessage.setSubject(mail.subject, mps.charset.name())
        // 邮件内容
        val mimeBodyPart = MimeBodyPart()
        when (mail.type) {
            // text格式
            Type.TEXT -> mimeBodyPart.setText(mail.body, mps.charset.name())
            // html格式
            Type.HTML -> mimeBodyPart.setContent(mail.body, "text/html;charset=${mps.charset.name()}")
        }
        // 邮件体
        val mimeMultipart = MimeMultipart("mixed")
        // 添加邮件内容
        mimeMultipart.addBodyPart(mimeBodyPart)
        // 附件
        mail.attachments?.forEach { attachment ->
            MimeBodyPart().apply {
                attachFile(attachment)
                mimeMultipart.addBodyPart(this)
            }
        }
        // 设置邮件体
        mimeMessage.setContent(mimeMultipart)
        // 发送时间
        mimeMessage.sentDate = Date()
        // 保存上面设置的邮件
        mimeMessage.saveChanges()
        return mimeMessage
    }


    /**
     * 最终操作发送方法。
     * @param message 一封邮件。
     * @return 发送结果。
     */
    fun send(message: Message): Boolean {
        return try {
            if (mps.auth) {
                Transport.send(message)
            } else {
                message.session.transport.use {
                    it.connect(mps.host, mps.port, mps.from, mps.password)
                    it.sendMessage(message, message.allRecipients)
                }
            }
            true
        } catch (err: MessagingException) {
            logger.error("邮件发送失败！", err)
            false
        }
    }

    /**
     * 发送邮件。
     * @param mail [Mail] 邮件内容。
     * @return 发送结果。
     */
    fun send(mail: Mail) = send(createMimeMessage(smtp, mail))

    /**
     * 发送邮件。
     * @param to          收件人(主要收件人)。
     * @param subject     邮件主题（标题）。
     * @param body        邮件内容（主体）。
     * @param attachments 附件。
     * @return 发送结果。
     */
    fun send(to: List<Addressee>, subject: String?, body: String?, attachments: List<File>?) =
        send(Mail(to, null, null, Type.HTML, subject, body, attachments))

    /**
     * 发送邮件。
     * @param addressee   收件人。
     * @param nickname    收件者昵称。
     * @param subject     邮件主题（标题）。
     * @param body        邮件内容（主体）。
     * @param attachments 附件。
     * @return 发送结果。
     */
    fun send(addressee: String, nickname: String?, subject: String?, body: String?, attachments: List<File>?): Boolean {
        val to = ArrayList<Addressee>().apply {
            add(Addressee(addressee, nickname))
        }
        return send(to, subject, body, attachments)
    }

    /**
     * 发送邮件。
     * @param addressee 收件人。
     * @param nickname  收件者昵称。
     * @param subject   邮件主题（标题）。
     * @param body      邮件内容（主体）。
     * @return 发送结果。
     */
    fun send(addressee: String, nickname: String?, subject: String?, body: String?) =
        send(addressee, nickname, subject, body, null)

    /**
     * 发送邮件。
     * @param addressee 收件人。
     * @param subject   邮件主题（标题）。
     * @param body      邮件内容（主体）。
     * @return 发送结果。
     */
    fun send(addressee: String, subject: String?, body: String?) = send(addressee, null, subject, body)

}
